import * as child_process from 'node:child_process';
import * as os from 'node:os';
import * as path from 'node:path';

import { Cache, CACHE_MANAGER } from '@nestjs/cache-manager';
import { Inject, Injectable, Logger, NotAcceptableException } from '@nestjs/common';
import { ConfigService } from '@nestjs/config';
import * as dayjs from 'dayjs';
import * as emptyDir from 'empty-dir';
import * as fse from 'fs-extra';
import * as glob from 'glob';
import * as lodash from 'lodash';
import * as nodemailer from 'nodemailer';
import { type Browser } from 'puppeteer-core';
import { combineLatest, filter, firstValueFrom, from, map, of, toArray } from 'rxjs';

import scheduleConfig, { type IIpMap, type ITask } from '../../../../../.schedule.config';
import { type IConfiguration, type IEnvConfig } from '../../../../config/configuration';
import { type IFolder } from '../../../../interface.d';

// eslint-disable-next-line @typescript-eslint/no-var-requires
const puppeteer = require('puppeteer-extra');

// eslint-disable-next-line @typescript-eslint/no-var-requires
const StealthPlugin = require('puppeteer-extra-plugin-stealth');

puppeteer.use(StealthPlugin());

type IFolderMaps = Record<IFolder, ITask & IIpMap>;

@Injectable()
export class DataFactory {
  @Inject(ConfigService)
  private configService: ConfigService<IEnvConfig & IConfiguration>;

  private readonly logger = new Logger(DataFactory.name);

  @Inject(CACHE_MANAGER)
  private cacheManager: Cache;

  mailNotice(subject: string, text: string) {
    console.log('AT-[ mailNotice &&&&&********** ]');

    const transporter = nodemailer.createTransport({
      port: this.configService.get('port'),
      secure: true,

      host: this.configService.get('host'),
      auth: {
        user: this.configService.get('user'),
        pass: this.configService.get('pass'),
      },
    });

    transporter.sendMail({
      from: `"kenny" <shanjiandeshu@163.com>`,
      to: '879464294@qq.com',
      subject,
      text,
    });
  }

  getMasterIp() {
    const ipMap = this.configService.get<IConfiguration['ipMap']>('ipMap');
    return ipMap.find(i => i.isMaster)?.['ip'];
  }

  createBackupPath() {
    const YTD = dayjs().format('YYYY-MM-DD');
    const desktopPath = this.getDesktopPath();
    const backupPath = path.join(desktopPath, 'history', YTD);

    fse.ensureDirSync(backupPath);

    return backupPath;
  }

  createBaseData() {
    const { tasks, ipMap } = scheduleConfig;

    const YTD = dayjs().format('YYYY-MM-DD');

    const folderMaps = ipMap.reduce<IFolderMaps>((pre, cur) => Object.assign(pre, { [cur.folder]: cur }), {});

    const observable = combineLatest([of(folderMaps), from(tasks)])
      /**将ip和目录对应上 */
      .pipe(map(([folderMap, task]) => ({ ...task, ...folderMap[task.folder] })))

      /**将时间转化为node-schedule可识别的类型 */
      .pipe(map(i => ({ ...i, time: new Date(`${YTD} ${i.time}`) })))

      .pipe(map(i => ({ ...i, cacheDirName: i.ip })))

      // 整体合成一个数组
      .pipe(toArray());

    return firstValueFrom(observable);
  }

  async createRealTimeData() {
    const baseData = await this.createBaseData();

    const observable = from(baseData)
      .pipe(filter(i => dayjs().isBefore(i.time)))

      // 整体合成一个数组
      .pipe(toArray());
    // .pipe(
    //   map(arr => [
    //     ...arr,
    //     {
    //       cacheDirName: 'test',
    //       time: new Date(Date.now() + 2 * 1000),
    //       folder: '01',
    //       // platforms: ['xhs', 'douyin', 'bili'],
    //       platforms: ['xhs'],
    //       ip: '192.168.1.1',
    //       isMaster: false,
    //     },
    //   ]),
    // );

    return firstValueFrom(observable);
  }

  async createBroswer(cacheDirName: string, proxyUrl?: string): Promise<Browser> {
    const userDataDir = path.join('.cache', cacheDirName);

    fse.ensureDirSync(userDataDir);

    const browser =
      (await this.cacheManager.get<Browser>('browser')) ||
      (await puppeteer.launch({
        channel: 'chrome',
        defaultViewport: null,
        headless: false,
        userDataDir,

        executablePath: '',
        args: [
          '--disable-cache',
          '--disable-gpu',
          '--disable-dev-shm-usage',
          '--no-first-run',
          // '--no-sandbox',
          '--no-zygote',
          // '--disable-setuid-sandbox',
          '--disable-notifications',
          '--disable-extensions',
          '--disable-geolocation',
          /**禁用websocket */
          '--disable-web-socket',
          /**关闭网页内容安全策略 即：允许跨域 */
          // '--disable-web-security',

          /** 禁用获取地理位置 */
          '--disable-features=Geolocation',

          proxyUrl ? `--proxy-server=${proxyUrl}` : '',
        ],

        /** 去掉正在被自动化测试 */
        ignoreDefaultArgs: ['--enable-automation', '--disable-blink-features=AutomationControlled'],
      }));

    browser.on('error', async error => {
      this.logger.error('AT-[ browser error &&&&&********** ]', error);
      await browser.close();
    });

    browser.on('disconnected', async () => {
      this.logger.error('puppeteer 程序断开连接');
      await browser.close();
    });

    /** 全局拦截一下 */
    browser.on('targetcreated', async target => {
      if (target.type() == 'page') {
        // console.log('剔除敏感属性');
        /** 剔除敏感属性 */

        const page = await target.page();

        page.on('error', async error => {
          this.logger.error(error);
          await browser.close();
          throw error;
        });

        // page.on('pageerror', async pageerror => {
        //   console.log('AT-[ pageerror &&&&&********** ]', pageerror);
        //   this.logger.error(pageerror);
        //   await browser.close();
        //   throw pageerror;
        // });
      }
    });

    return browser;
  }

  clearEmptyCourseDir() {
    const { ipMap } = scheduleConfig;

    const desktopPath = this.getDesktopPath();

    const dirPattern = path.join(desktopPath, `t_{${ipMap.map(i => i.folder).toString()}}`, '**/*/');

    const allDirs = glob.sync(dirPattern);

    allDirs.forEach(dir => emptyDir.sync(dir) && fse.rmdirSync(dir));
  }

  getDesktopPath() {
    return path.join(os.homedir(), 'Desktop');
  }

  setPowershellUtf8() {
    /** windows系统设置pwoershell的编码方式，以免乱码 */
    if (!os.platform().startsWith('win')) return;

    // 使用 PowerShell 命令设置控制台编码为 UTF-8
    const powershellCmd = 'powershell';

    const powershellArgs = ['-Command', '$OutputEncoding = [Console]::OutputEncoding = [System.Text.Encoding]::UTF8'];

    // 执行 PowerShell 命令
    child_process.spawnSync(powershellCmd, powershellArgs);
  }

  getAllnetshInterface() {
    if (!os.platform().startsWith('win')) return;

    const ls = child_process.spawnSync('netsh', ['interface', 'show', 'interface'], {
      encoding: 'utf-8',
    });

    const interfaces = ls.stdout
      .toString()
      .split('\r\n')
      .filter(Boolean)
      .slice(-2)
      .map(i => i.split(' ').filter(Boolean))
      .map(i => Object.fromEntries(lodash.zip(['admin', 'state', 'type', 'name'], i)));

    return interfaces;
  }

  setIp(ip: string) {
    if (os.platform() === 'darwin') {
      this.logger.error('Mac system does not support set ip');
      return null;
    }

    this.setPowershellUtf8();

    const interfaces = this.getAllnetshInterface().filter(i => i.state);

    if (!interfaces.some(i => i.name === ip)) {
      // throw new Error('这台机器上没有ip：', ip);
      throw new NotAcceptableException(`这台机器上没有ip：${ip}`);
    }

    for (const item of interfaces) {
      const admin = ip == item.name ? 'enable' : 'disable';

      child_process.spawnSync('netsh', ['interface', 'set', 'interface', item.name, `admin=${admin}`], {
        encoding: 'utf-8',
      });
    }
  }

  setMasterIp() {
    const masterIp = this.getMasterIp();

    this.setIp(masterIp);
  }

  cacheErr(fn: (...rest) => any) {
    try {
      fn();
    } catch (error) {
      this.logger.error(error);
      throw new Error(error);
    }
  }
}
